package cn.b504.qpcr.impls.cal;

import cn.b504.qpcr.entity.AnalysisMethodSetting;
import cn.b504.qpcr.entity.Data;
import cn.b504.qpcr.entity.PlateSetting;
import cn.b504.qpcr.entity.Well;
import cn.b504.qpcr.exception.ListsBoundsNotEqualException;
import cn.b504.qpcr.interfaces.cal.CalCq;
import cn.b504.qpcr.interfaces.cal.CalMeanAndSd;
import cn.b504.qpcr.utils.CalCqUtils;
import cn.b504.qpcr.utils.MathUtils;
import cn.b504.qpcr.utils.Utils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

/**
 * @author ssc
 */
@Service
public class CalCqImpl implements CalCq {
  private final CalMeanAndSd calMeanAndSd;

  public CalCqImpl(CalMeanAndSd calMeanAndSd) {
    this.calMeanAndSd = calMeanAndSd;
  }

  /*@Override
  public Future<?> calByThreshold(PlateSetting.Target target, Data data, final int startCycle, final int endCycle, final String method) {
    final short channelIndex = target.getChannelIndex();
    final String targetName = target.getName();
    final List<Float> stdevp10List = new ArrayList<>();
    data.getWells().forEach(well -> {
      List<Float> list = CalCqUtils.sgFilterList(well, targetName, channelIndex, method);
      if (list == null) {
        return;
      }
      if (CalCqUtils.isError(list)) {
        return;
      }
      //  TODO:改了循环
//      stdevp10List.add(CalCqUtils.getStdevp10FromRange(list, startCycle, endCycle));

      int start = well.getLines()[channelIndex].getBaseStartCycle();
      int end = well.getLines()[channelIndex].getBaseEndCycle();
      stdevp10List.add(CalCqUtils.getStdevp10FromRange(list, start, end));
    });
    float stdevp10 = MathUtils.getAverageOfList(stdevp10List, -1);
    target.setThreshold(stdevp10);
    data.getWells().forEach(well -> {
      //TODO: omit不参与计算
      if (!well.getLines()[channelIndex].getOmit()) {
        List<Float> list = CalCqUtils.sgFilterList(well, targetName, channelIndex, method);
        if (list == null) {
          return;
        }
        if (CalCqUtils.isError(list)) {
          return;
        }
        float cqValue = MathUtils.y2x(list, stdevp10);
        well.getLines()[channelIndex].setCq(cqValue);
      }
    });
    return new AsyncResult<>(null);
  }
 */

  //  TODO：新算法
  @Override
  public Future<?> calByThreshold(PlateSetting.Target target, Data data, final int startCycle, final int endCycle, final String method) {
    final short channelIndex = target.getChannelIndex();
    final String targetName = target.getName();
    final List<Float> stdevp10List = new ArrayList<>();
    ArrayList<Float> allStdevp = new ArrayList<>();

    data.getWells().forEach(well -> {
      float sigLevel = well.getLines()[channelIndex].getSigLevel();
      List<Float> list = CalCqUtils.sgFilterList(well, targetName, channelIndex, method);
      List<Float> newList = CalCqUtils.newSgFilterList(well, targetName, channelIndex, method);

      List<Float> originList = CalCqUtils.getData(well.getLines()[channelIndex], AnalysisMethodSetting.Method.RAW_DATA);

      if (newList == null) {
        return;
      }
//      float cqValue = CalCqUtils.calByExtremum(originList, newList,sigLevel);
      if (list == null) {
        return;
      }
//      if (CalCqUtils.isError(list)) {
//        return;
//      }
      if (CalCqUtils.isError(originList, sigLevel)) {
        return;
      }
      int start = well.getLines()[channelIndex].getBaseStartCycle();
      int end = well.getLines()[channelIndex].getBaseEndCycle();

//      int newEnd = (int) Math.floor(cqValue) - 2;
//      well.getLines()[channelIndex].setBaseEndCycle(newEnd);
      if (end < 5) {
        return;
      }

//      for (int i = start; i < end; i++) {
//        allStdevp.add(list.get(i));
//      }

      stdevp10List.add(CalCqUtils.getStdevp10FromRange(list, start, end));
    });
//    float allStdevp10 = CalCqUtils.getNewStdevp(allStdevp) * 10;//  新算法
    float stdevp10 = MathUtils.getAverageOfList(stdevp10List, -1); // 阈值线的值
    target.setThreshold(stdevp10);
    data.getWells().forEach(well -> {
      float sigLevel = well.getLines()[channelIndex].getSigLevel();
      //TODO: omit不参与计算
      if (!well.getLines()[channelIndex].getOmit()) {
        List<Float> list = CalCqUtils.sgFilterList(well, targetName, channelIndex, method);
        List<Float> originList = CalCqUtils.getData(well.getLines()[channelIndex], AnalysisMethodSetting.Method.RAW_DATA);
        if (list == null) {
          return;
        }
//        if (CalCqUtils.isError(list)) {
//          return;
//        }
        if (CalCqUtils.isError(originList, sigLevel)) {
          return;
        }
        float cqValue = MathUtils.y2x(list, stdevp10);
        well.getLines()[channelIndex].setCq(cqValue);
      }
    });
    return new AsyncResult<>(null);
  }

  @Override
  public Future<?> calByCustomThreshold(PlateSetting.Target target, Data data, String method) {
    final short channelIndex = target.getChannelIndex();

    float stdevp10 = target.getThreshold();
    data.getWells().forEach(well -> {
      float sigLevel = well.getLines()[channelIndex].getSigLevel();
      List<Float> list = CalCqUtils.sgFilterList(well, target.getName(), channelIndex, method);
      List<Float> originList = CalCqUtils.getData(well.getLines()[channelIndex], AnalysisMethodSetting.Method.RAW_DATA);
      if (list == null) {
        return;
      }
//      if (CalCqUtils.isError(list)) {
//        return;
//      }

      if (CalCqUtils.isError(originList, sigLevel)) {
        return;
      }
      float cqValue = MathUtils.y2x(list, stdevp10);
      well.getLines()[channelIndex].setCq(cqValue);
    });
    return new AsyncResult<>(null);
  }

  @Override
  @Async
  public Future<?> calByRegression(Well.Line line, int startCycle, int endCycle, String method) {
    float sigLevel = line.getSigLevel();
    List<Float> list = CalCqUtils.getData(line, method);
    List<Float> originList = CalCqUtils.getData(line, AnalysisMethodSetting.Method.RAW_DATA);
    List<Float> dataList = CalCqUtils.sgFilter(MathUtils.backgroundRemoval(list));
    float cqValue = CalCqUtils.calByRegression(originList, dataList, startCycle, endCycle, sigLevel);
    line.setCq(cqValue);
    return new AsyncResult<>(null);
  }

  @Override
  @Async
  public Future<?> calByExtremum(Well.Line line, String method) {
    //  TODO: 前端改还是后端改?
    float sigLevel = line.getSigLevel();
    List<Float> list = CalCqUtils.getData(line, method);
    List<Float> originList = CalCqUtils.getData(line, AnalysisMethodSetting.Method.RAW_DATA);
    if (list.size() == 0) {
      line.setCq(-1);
    } else {
      List<Float> dataList = CalCqUtils.sgFilter(MathUtils.backgroundRemoval(list));
      float cqValue = CalCqUtils.calByExtremum(originList, dataList,sigLevel);
      line.setCq(cqValue);
    }
    return new AsyncResult<>(null);
  }

  @Override
  public void calByCqMan(final Data data, String method) {

    final List<Future<?>> list = new ArrayList<>();
    // 以 3 - 15 个循环的单一阈值法为首选算法和计算结果
    AnalysisMethodSetting setting = data.getAnalysisMethodSetting();
    final int startCycle = setting.getStartCycle();
    final int endCycle = setting.getEndCycle();
//    data.getTargets().forEach(it -> it.setThreshold((float) 0.01));
    data.getTargets().forEach(it -> list.add(this.calByThreshold(it, data, startCycle, endCycle, method)));
    Utils.ensureEmptyFutureList(list);
    data.getTargets().forEach(it -> list.add(calMeanAndSd.cq(it, data)));
    Utils.ensureEmptyFutureList(list);

    final Data dataCloneRegression = data.clone();
    dataCloneRegression.getWells().forEach(it -> {
      Well.Line[] lines = it.getLines();
      for (Well.Line line : lines) {
        if (line.getOmit()) {
          continue;
        }
        list.add(this.calByRegression(line, line.getBaseStartCycle(), line.getBaseEndCycle(), method));
      }
    });
    Utils.ensureEmptyFutureList(list);
    dataCloneRegression.getTargets().forEach(it -> list.add(calMeanAndSd.cq(it, dataCloneRegression)));
    Utils.ensureEmptyFutureList(list);
//    System.out.println("dataCloneRegression = "+ data.getWells().size()+"1111"+dataCloneRegression.getWells().size());
    try {
      compareAndChangeCq(data.getWells(), dataCloneRegression.getWells());
    } catch (ListsBoundsNotEqualException e) {
      e.printStackTrace();
      return;
    }

    final Data dataCloneExtremum = data.clone();
    dataCloneExtremum.getWells().forEach(it -> {
      Well.Line[] lines = it.getLines();
      for (Well.Line line : lines) {
        if (line.getOmit()) {
          continue;
        }
        list.add(this.calByExtremum(line, method));
      }
    });
    Utils.ensureEmptyFutureList(list);
    dataCloneExtremum.getTargets().forEach(it -> list.add(calMeanAndSd.cq(it, dataCloneExtremum)));
    Utils.ensureEmptyFutureList(list);
    try {
      compareAndChangeCq(data.getWells(), dataCloneExtremum.getWells());
    } catch (ListsBoundsNotEqualException e) {
      e.printStackTrace();
    }
  }

  private void compareAndChangeCq(List<Well> wells, List<Well> wellsClone) throws ListsBoundsNotEqualException {
    if (wells.size() != wellsClone.size()) {
      throw new ListsBoundsNotEqualException();
    }
    for (int i = 0; i < wells.size(); i++) {
      Well.Line[] lines = wells.get(i).getLines();
      Well.Line[] linesClone = wellsClone.get(i).getLines();
      for (int j = 0; j < lines.length; j++) {
        if (lines[j].getOmit() || linesClone[j].getCq() == -1) {
          continue;
        }
        if (lines[j].getCq() == -1 && linesClone[j].getCq() != -1) {
          replaceCq(lines[j], linesClone[j]);
          continue;
        }
        if (lines[j].getCqSd() != -1 && linesClone[j].getCqSd() == -1) {
          continue;
        }
        if (lines[j].getCqSd() == -1 && linesClone[j].getCqSd() != -1) {
          replaceCq(lines[j], linesClone[j]);
          continue;
        }
        if (lines[j].getCqSd() < linesClone[j].getCqSd()) {
          continue;
        }

        if (lines[j].getCqSd() > linesClone[j].getCqSd() || lines[j].getCq() > linesClone[j].getCq()) {
          replaceCq(lines[j], linesClone[j]);
        }
      }
    }
  }

  private void replaceCq(Well.Line line, Well.Line lineClone) {
    line.setCq(lineClone.getCq());
    line.setCqMean(lineClone.getCqMean());
    line.setCqSd(lineClone.getCqSd());
  }
}
